package com.katze.boot.file.servlet;


import com.katze.boot.file.entity.FileTransfer;
import com.katze.common.http.HttpUtils;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload2.core.DiskFileItem;
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
import org.apache.commons.io.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.openmbean.KeyAlreadyExistsException;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class DefaultFileTransferServlet extends HttpServlet {

    private static final long serialVersionUID = -5586332524956738690L;

    private static final Logger log = LoggerFactory.getLogger(DefaultFileTransferServlet.class);

    private static final Charset DEFAULT_CHARSET = Charsets.toCharset("utf-8");

    private final Map<String, IFileUploadService> service = new HashMap<>();

    private File repository;

    public DefaultFileTransferServlet(String temp) {
        this.repository = new File(temp);
    }

    /**
     * 文件上传前断点校验
     * @param request  an {@link HttpServletRequest} object that contains the request the client has made of the servlet
     * @param response an {@link HttpServletResponse} object that contains the response the servlet sends to the client
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        StringBuilder builder = new StringBuilder();
        try {
            FileTransfer transfer = HttpUtils.toBean(request, FileTransfer.class).temp(repository); //请求信息转传输对象
            IFileUploadService handle = service.containsKey(transfer.getService())? service.get(transfer.getService()):service.get("default");
            String[] chunks = handle.beforeUpload(transfer);
            builder.append("{").append("\"code\":").append(200).append(",\"exists\":").append(Arrays.toString(chunks)).append("}");
        } catch (Exception e) {
            log.error("校验文件断点时发生异常", e);
            builder.append("{").append("\"code\":").append(500).append(",\"message\":\"").append("校验断点信息失败").append("\"}");
        }
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(builder.toString());
    }

    /**
     * 文件上传
     * @param request  an {@link HttpServletRequest} object that contains the request the client has made of the servlet
     * @param response an {@link HttpServletResponse} object that contains the response the servlet sends to the client
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        StringBuilder builder = new StringBuilder();
        if (!JakartaServletDiskFileUpload.isMultipartContent(request)) {
            builder.append("{").append("\"code\":").append(414).append(",\"message\":\"").append("非表单数据无法上传").append("\"}");
        } else {
            try {
                FileTransfer transfer = loadRequest(request).temp(repository);
                IFileUploadService handle = service.containsKey(transfer.getService())? service.get(transfer.getService()):service.get("default");
                handle.upload(transfer);
                builder.append("{").append("\"code\":").append(200).append("}");
            } catch (Throwable e) {
                log.error("上传文件时发生异常", e);
                builder.append("{").append("\"code\":").append(500).append(",\"message\":\"").append("上传文件失败").append("\"}");
            }
        }
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(builder.toString());
    }

    /**
     * 文件上传完成后合并文件块
     * @param request  the {@link HttpServletRequest} object that contains the request the client made of the servlet
     * @param response the {@link HttpServletResponse} object that contains the response the servlet returns to the client
     */
    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException {
        StringBuilder builder = new StringBuilder();
        try {
            FileTransfer transfer = HttpUtils.toBean(request, FileTransfer.class).temp(repository);
            IFileUploadService handle = service.containsKey(transfer.getService())? service.get(transfer.getService()):service.get("default");
            handle.afterUpload(transfer);
            builder.append("{").append("\"code\":").append(200).append("}");
        } catch (Throwable e) {
            log.error("合并文件时发生异常", e);
            builder.append("{").append("\"code\":").append(500).append(",\"message\":\"").append("合并文件失败").append("\"}");
        }
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(builder.toString());
    }

    /**
     * 解析上传请求
     * @param request 请求
     */
    private FileTransfer loadRequest(HttpServletRequest request) throws IOException, IllegalAccessException {
        JakartaServletDiskFileUpload uploadServlet = new JakartaServletDiskFileUpload();
        DiskFileItemFactory factory = DiskFileItemFactory.builder().setFile(this.repository).setCharset(DEFAULT_CHARSET).get();
        uploadServlet.setFileItemFactory(factory);
        FileTransfer transfer = new FileTransfer();
        List<DiskFileItem> fileItems = uploadServlet.parseRequest(request);
        for (DiskFileItem item : fileItems) {
            try {
                if (item.isFormField()) {
                    Field field = FileTransfer.class.getDeclaredField(item.getFieldName());
                    field.setAccessible(true);
                    if (field.getType().isAssignableFrom(Long.class)){
                        field.set(transfer, Long.parseLong(item.getString()));
                    } else if (field.getType().isAssignableFrom(int.class)){
                        field.set(transfer, Integer.parseInt(item.getString()));
                    } else {
                        field.set(transfer, item.getString(DEFAULT_CHARSET));
                    }
                } else {
                    transfer.setData(item.get());
                    if (transfer.getName() == null) {
                        String filename = item.getName();
                        int pos = Math.max(filename.lastIndexOf('\\'), filename.lastIndexOf('/'));
                        transfer.setName(pos != -1? filename.substring(pos + 1):filename);
                    }
                }
            } catch (NoSuchFieldException e) {
                if (log.isDebugEnabled()) log.warn("字段名:{} 不存在", item);
            } finally {
                if (!item.isFormField()) {
                    item.delete();
                }
            }
        }
        return transfer;
    }

    public IFileUploadService add(String name, IFileUploadService handle) {
        if (service.containsKey(name)) {
            throw new KeyAlreadyExistsException(name + " already exist!");
        }
        return  service.put(name, handle);
    }

    public void setRepository(String repository) {
        // todo 优化上传过程中修改路径逻辑
        this.repository = new File(repository);
    }
}
